//
//  HStore.m
//  TestOCRedux
//
//  Created by Hotacool on 2019/1/17.
//  Copyright © 2019 Hotacool. All rights reserved.
//

#import "HStore.h"

#pragma mark - HSubscription
@interface HSubscription : NSObject
@property (nonatomic, weak) id<HSubscriberType> subscriber;
@property (nonatomic, strong) NSString *stateClassName;
@end
@implementation HSubscription
- (instancetype)initWithSubscriber:(id<HSubscriberType>)subscriber {
    if (self = [super init]) {
        self.subscriber = subscriber;
    }
    return self;
}

- (void)notifyIfNeedFromOld:(id<HStateType>)ost toNew:(id<HStateType>)nst action:(id<HActionType>)action {
    if (self.subscriber) {
        if (self.stateClassName) {
            id<HStateType> subState = [self getSpecifyState:nst];
            id<HStateType> oldSubState = [self getSpecifyState:ost];
            if (subState) {
                if (subState != oldSubState) {//changed
                    [self.subscriber stateChanged:subState action:action];
                }
            }
        } else {
            [self.subscriber stateChanged:nst action:action];
        }
    }
}

- (id<HStateType>)getSpecifyState:(id<HStateType>)state {
    if ([state isKindOfClass:[HState class]]) {
        return ((HState*)state)[self.stateClassName];
    }
    return nil;
}
@end
#define SubscriptionBox(x) ([[HSubscription alloc] initWithSubscriber:x])
#pragma mark - HStore
@interface HStore ()
@property (nonatomic, strong) HReducer reducer;
@property (nonatomic, strong, readwrite) id state;
@property (nonatomic, strong) NSMutableArray<HSubscription*> *subscriptonList;

@property (nonatomic, strong, readwrite) id oldState;
@end

@implementation HStore

+ (dispatch_queue_t)serialQueue {
    static dispatch_queue_t serialQueue = NULL;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        serialQueue = dispatch_queue_create("HStore.serialQueue", DISPATCH_QUEUE_CONCURRENT);
        // we use the highpri queue to prioritize UI rendering over other async operations
        dispatch_set_target_queue(serialQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
    });
    return serialQueue;
}

+ (HStore*)generateWithReduce:(id)reduce state:(id)state {
    HStore *store = [HStore new];
    store.reducer = reduce;
    store.state = state;
    store.subscriptonList = [NSMutableArray array];
    return store;
}

- (void)dispatch:(id<HActionType>)action {
    if (self.reducer && action) {
        dispatch_async([HStore serialQueue], ^{
            self.oldState = self.state;
            id newState = self.reducer(action, self.state);
            if (newState) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    [self updateState:newState withAction:action];
                });
            }
        });
    }
}

- (void)subscribe:(id<HSubscriberType>)subscriber {
    if (subscriber) {
        [self.subscriptonList addObject:SubscriptionBox(subscriber)];
    }
}

- (void)subscribe:(id<HSubscriberType>)subscriber specifySubState:(NSString*)subStateName {
    if (subscriber) {
        HSubscription *subscription = SubscriptionBox(subscriber);
        subscription.stateClassName = subStateName;
        [self.subscriptonList addObject:subscription];
    }
}

- (void)unSubscribe:(id<HSubscriberType>)subscriber {
    if (subscriber) {
        NSInteger __block index = -1;
        [self.subscriptonList enumerateObjectsUsingBlock:^(HSubscription * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            if (obj.subscriber == subscriber) {
                index = idx;
                *stop = YES;
            }
        }];
        if (index >= 0) {
            [self.subscriptonList removeObjectAtIndex:index];
        }
    }
}

- (void)updateState:(id<HStateType>)state withAction:(id<HActionType>)action {
    _state = state;
    [self.subscriptonList enumerateObjectsUsingBlock:^(HSubscription * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        [obj notifyIfNeedFromOld:self.oldState toNew:state action:action];
    }];
}
@end

